# encoding: utf-8

import urllib
# HTMLParser在endtag上行为难以捉摸，遂用SGMLParser。
from sgmllib import SGMLParser, SGMLParseError
from constants import *

#<body> 0
#  <div id="page-wrapper"> 1
#    <div id="page-body"> 2
#      <div id="wrap"> 3
#        <div id="content"> 4
#          <div id="contentInfo"> 5
#            <h1 id="topicstitle">Title</h1> 6
#            <div class="topicImg"> 6
#              <a id="topicImgUrl"> 7
#                <img src="..." /> 8
#              </a>
#            </div>
#          </div>
#          <div id="blog_entry"> 5
#            <div id="iptcomED2K"> 6
#              <div class="emulemain"> 7
#                <table> 8
#                  <tbody> 8 (注:tbody不会进入starttag)
#                    <tr> 9
#                      <td class="post2"> 10
#                        <a ed2k="ed2k link here" href="the same as ed2k">file name (e.g., tar file)</a> 11
#            <div id="iptcomEname"> 6
#              <span><strong>专辑英文名</strong></span>
#              English Name Here
#            </div>
#            <div id="iptcomCname"> 6
#              <span><strong>专辑中文名</strong></span>
#              此处为中文名
#            </div>
#            <div id="iptcomTrack"> 6
#              <p class="inner_content"> 7
#                Track info here.
#              </p>
#            </div>
#            <div id="iptcomContents"> 6
#              <p class="inner_content"> 6 (!!!)
#                <span class="post_origin_img" originlink="image link"> 7
#                <span class="post_origin_img" originlink="image link">
#            </div>
#          </div>
#        </div>
#      </div>
#    </div>
#  </div>
#</body>

# 给定
#   <div id="iptcomEname"> 6
#     <span><strong>专辑英文名</strong></span>
#       English Name Here
#   </div>
# SGMLParser的方法调用顺序是：
# - start_div
#   - start_span
#     - start_strong
#     - end_strong
#   - end_span
# - start_div

def get_attr(attrs, key):
    for (k, v) in attrs:
        if k == key:
            return v
    return None

class VeryCdParser(SGMLParser):
    TITLE_LEVEL = 6
    THUMBNAIL_LEVEL = 7
    IMAGE_LEVEL = 7
    ED2K_LEVEL = 11
    ACTOR_LEVEL = 8
    PROPERTY_LEVEL = 6
    PROPERTIES = [ ENAME, CNAME, KIND, FILETYPE, DATE, INTRO, TRACK ]

    # 遇到如下这些tag时增减level，但是没有data可以处理。
    # 增减level在unknown_start(/end)tag里处理，之所以不对所有的tag增减level，
    # 是因为防止某些怪异的tag影响level的正确性。
    LEVEL_ONLY_TAGS = ['strong', 'table', 'td', 'tr']

    # 如下项目的内容需要考虑<br>。
    BR_ITEMS = [ INTRO, TRACK ]

    def reset(self):
        SGMLParser.reset(self)

        self.level = 0
        self.values = {}
        self.item = None
        self.inside = False
        self.inside_level = 0
        self.sub_inside = False
        self.ed2k_link = None

    # TODO
    def __add_value(self, item, value):
        if item in self.values:
            self.values[item] = self.values[item] + value
        else:
            self.values[item] = value

    def __add_ed2k(self, ed2k_file, ed2k_link):
        self.values.setdefault(ED2K, []).append((ed2k_file, ed2k_link))

    def __add_actor(self, actor):
        self.values.setdefault(ACTOR, []).append(actor)

    def __clear_inside(self):
        if self.inside and self.inside_level == self.level:
            self.inside = False
            self.inside_level = 0
            self.item = None

    def __clear_sub_inside(self):
        if self.sub_inside and self.sub_inside_level == self.level:
            self.sub_inside = False
            self.sub_inside_level = 0

    def __post_process(self, item):
        """Trim empty spaces, remove redundant empty lines, etc."""
        if item not in self.values:
            return
        self.values[item] = '\n'.join(filter(lambda x: len(x) > 0,
            map(lambda x: x.strip(), self.values[item].split(u'\n'))))

    def post_process(self):
        self.__post_process(INTRO)
        self.__post_process(TRACK)

    def __inc_level(self):
        self.level = self.level + 1

    def __dec_level(self):
        self.level = self.level - 1

    def start_h1(self, attrs):
        self.__inc_level()

        if self.level == self.TITLE_LEVEL and get_attr(attrs, 'id') == 'topicstitle':
            self.item = TITLE
            self.inside = True
            self.inside_level = self.level

    def end_h1(self):
        self.__clear_inside()
        self.__dec_level()

    def start_div(self, attrs):
        self.__inc_level()

        if self.level == self.PROPERTY_LEVEL:
            if get_attr(attrs, 'id') == 'iptcomEname':
                self.item = ENAME
            elif get_attr(attrs, 'id') == 'iptcomCname':
                self.item = CNAME
            elif get_attr(attrs, 'id') == 'iptcomKind':
                self.item = KIND
            elif get_attr(attrs, 'id') == 'iptcomFiletype':
                self.item = FILETYPE
            elif get_attr(attrs, 'id') == 'iptcomTime':
                self.item = DATE
            elif get_attr(attrs, 'id') == 'iptcomContents':
                self.item = INTRO
            elif get_attr(attrs, 'id') == 'iptcomTrack':
                self.item = TRACK
            elif get_attr(attrs, 'id') == 'iptcomED2K':
                self.item = ED2K
            elif get_attr(attrs, 'id') == 'iptcomActor':
                self.item = ACTOR

            if self.item:
                self.inside = True
                self.inside_level = self.level

    def end_div(self):
        self.__clear_inside()
        self.__dec_level()

    def unknown_starttag(self, tag, attrs):
        if tag in self.LEVEL_ONLY_TAGS:
            self.__inc_level()

    def unknown_endtag(self, tag):
        if tag in self.LEVEL_ONLY_TAGS:
            self.__dec_level()

    def start_a(self, attrs):
        self.__inc_level()
        if not self.item:
            if self.level == self.THUMBNAIL_LEVEL\
                and get_attr(attrs, 'id') == 'topicImgUrl':
                self.item = THUMBNAIL
                self.inside = True
                self.inside_level = self.level
                return

        if self.item == ED2K:
            self.ed2k_link = get_attr(attrs, 'ed2k')
            if self.level == self.ED2K_LEVEL and self.ed2k_link:
                self.sub_inside = True
        elif self.item == ACTOR:
            if self.level == self.ACTOR_LEVEL:
                self.sub_inside = True

        if self.sub_inside:
            self.sub_inside_level = self.level

    def end_a(self):
        self.__clear_sub_inside()
        self.__dec_level()

    def start_img(self, attrs):
        if self.item == THUMBNAIL:
            src = get_attr(attrs, 'src')
            self.__add_value(THUMBNAIL, src)

    def end_img(self):
        pass

    def start_span(self, attrs):
        self.__inc_level()
        if self.level == self.IMAGE_LEVEL:
            if get_attr(attrs, 'class') == 'post_origin_img':
                self.values.setdefault(IMAGE, []).append(get_attr(attrs, 'originlink'))

    def end_span(self):
        self.__dec_level()

    def start_br(self, attrs):
        if self.item and self.item in self.BR_ITEMS:
            self.__add_value(self.item, '\n')

    def end_br(self):
        pass

    def __is_to_add_data(self):
        if self.item == TITLE:
            if self.level == self.TITLE_LEVEL:
                return True
        if self.level == self.PROPERTY_LEVEL:
            if self.item and self.item in self.PROPERTIES:
                return True
        return False

    def handle_data(self, data):
        if not self.item:
            return

        if self.__is_to_add_data():
            self.__add_value(self.item, data)
        elif self.item == ED2K:
            if self.sub_inside:
                self.__add_ed2k(data, self.ed2k_link)
        elif self.item == ACTOR:
            if self.sub_inside:
                self.__add_actor(data)

def grab(url):
    try:
        p = VeryCdParser()
        p.feed(unicode(urllib.urlopen(url).read(), 'utf-8'))
        p.close()
        p.post_process()
    except IOError: # urlopen()
        return None
    except ValueError: # unicode()
        return None
    except SGMLParseError:
        return None
    return p.values

